// baljsn_encoder.h                                                   -*-C++-*-
#ifndef INCLUDED_BALJSN_ENCODER
#define INCLUDED_BALJSN_ENCODER

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide a JSON encoder for 'bdlat'-compatible types.
//
//@CLASSES:
// baljsn::Encoder: JSON decoder for 'bdlat'-compliant types
//
//@SEE_ALSO: baljsn_decoder, baljsn_printutil
//
//@DESCRIPTION: This component provides a class, 'baljsn::Encoder', for
// encoding value-semantic objects in the JSON format.  In particular, the
// 'class' contains a parameterized 'encode' function that encodes an object
// into a specified stream.  There are two overloaded versions of this
// function:
//
//: o one that writes to a 'bsl::streambuf'
//: o one that writes to an 'bsl::ostream'
//
// This component can be used with types that support the 'bdlat' framework
// (see the 'bdlat' package for details), which is a compile-time interface for
// manipulating struct-like and union-like objects.  In particular, types
// generated by the 'bas_codegen.pl' tool, and other dynamic types, can be
// encoded using this 'class'.  The 'encode' function can be invoked on any
// object that satisfies the requirements of a sequence, choice, or array
// object as defined in the 'bdlat_sequencefunctions', 'bdlat_choicefunctions',
// and 'bdlat_arrayfunctions' components.
//
// Although the JSON format is easy to read and write and is very useful for
// debugging, it is relatively expensive to encode and decode and relatively
// bulky to transmit.  It is more efficient to use a binary encoding (such as
// BER) if the encoding format is under your control (see 'balber_berencoder').
//
// Refer to the details of the JSON encoding format supported by this encoder
// in the package documentation file (doc/baljsn.txt).
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Encoding a 'bas_codegen.pl'-Generated Object into JSON
///-----------------------------------------------------------------
// Consider that we want to exchange an employee's information between two
// processes.  To allow this information exchange we will define the XML schema
// representation for that class, use 'bas_codegen.pl' to create the 'Employee'
// 'class' for storing that information, populate an 'Employee' object, and
// encode that object using the baljsn encoder.
//
// First, we will define the XML schema inside a file called 'employee.xsd':
//..
//  <?xml version='1.0' encoding='UTF-8'?>
//  <xs:schema xmlns:xs='http://www.w3.org/2001/XMLSchema'
//             xmlns:test='http://bloomberg.com/schemas/test'
//             targetNamespace='http://bloomberg.com/schemas/test'
//             elementFormDefault='unqualified'>
//
//      <xs:complexType name='Address'>
//          <xs:sequence>
//              <xs:element name='street' type='xs:string'/>
//              <xs:element name='city'   type='xs:string'/>
//              <xs:element name='state'  type='xs:string'/>
//          </xs:sequence>
//      </xs:complexType>
//
//      <xs:complexType name='Employee'>
//          <xs:sequence>
//              <xs:element name='name'        type='xs:string'/>
//              <xs:element name='homeAddress' type='test:Address'/>
//              <xs:element name='age'         type='xs:int'/>
//          </xs:sequence>
//      </xs:complexType>
//
//      <xs:element name='Employee' type='test:Employee'/>
//
//  </xs:schema>
//..
// Then, we will use the 'bas_codegen.pl' tool, to generate the C++ classes for
// this schema.  The following command will generate the header and
// implementation files for the all the classes in the 'test_messages'
// components in the current directory:
//..
//  $ bas_codegen.pl -m msg -p test xsdfile.xsd
//..
// Next, we will populate a 'test::Employee' object:
//..
//  test::Employee employee;
//  employee.name()                 = "Bob";
//  employee.homeAddress().street() = "Lexington Ave";
//  employee.homeAddress().city()   = "New York City";
//  employee.homeAddress().state()  = "New York";
//  employee.age()                  = 21;
//..
// Then, we will create a 'baljsn::Encoder' object:
//..
//  baljsn::Encoder encoder;
//..
// Now, we will output this object in the JSON format by invoking the 'encode'
// method of the encoder.  We will also create a 'baljsn::EncoderOptions'
// object that allows us to specify that the encoding should be done in a
// pretty format, and what the initial indent level and spaces per level should
// be.  We will then pass that object to the 'encode' method:
//..
//  bsl::ostringstream os;
//
//  baljsn::EncoderOptions options;
//  options.setEncodingStyle(baljsn::EncoderOptions::e_PRETTY);
//  options.setInitialIndentLevel(1);
//  options.setSpacesPerLevel(4);
//
//  const int rc = encoder.encode(os, employee, options);
//  assert(!rc);
//  assert(os);
//..
// Finally, we will verify that the output is as expected:
//..
//  const char EXP_OUTPUT[] = "    {\n"
//                            "        \"name\" : \"Bob\",\n"
//                            "        \"homeAddress\" : {\n"
//                            "            \"street\" : \"Lexington Ave\",\n"
//                            "            \"city\" : \"New York City\",\n"
//                            "            \"state\" : \"New York\"\n"
//                            "        },\n"
//                            "        \"age\" : 21\n"
//                            "    }\n";
//
//  assert(EXP_OUTPUT == os.str());
//..

#include <balscm_version.h>

#include <baljsn_encoderoptions.h>
#include <baljsn_formatter.h>
#include <baljsn_printutil.h>

#include <bdlat_attributeinfo.h>
#include <bdlat_choicefunctions.h>
#include <bdlat_customizedtypefunctions.h>
#include <bdlat_enumfunctions.h>
#include <bdlat_formattingmode.h>
#include <bdlat_sequencefunctions.h>
#include <bdlat_typecategory.h>
#include <bdlat_valuetypefunctions.h>

#include <bdlb_print.h>

#include <bdlsb_memoutstreambuf.h>

#include <bsls_assert.h>
#include <bsls_types.h>

#include <bsl_iostream.h>
#include <bsl_sstream.h>
#include <bsl_streambuf.h>
#include <bsl_string.h>
#include <bsl_vector.h>

namespace BloombergLP {
namespace baljsn {

                               // =============
                               // class Encoder
                               // =============

class Encoder {
    // This class provides a mechanism for encoding value-semantic objects in
    // the JSON format.  The 'encode' methods are function templates that will
    // encode any object that meets the requirements of a sequence, choice, or
    // array object as defined in the 'bdlat_sequencefunctions',
    // 'bdlat_choicefunctions', and 'bdlat_choicefunctions' components
    // respectively.  These generic frameworks provide a common compile-time
    // interface for accessing struct-like and union-like objects.  In
    // particular, the types generated by 'bas_codegen.pl' provide the
    // necessary interface and can be encoded using this component.

    // DATA
    bsl::ostringstream d_logStream;  // stream used for logging

    // NOT IMPLEMENTED
    Encoder(const Encoder&);

    // PRIVATE MANIPULATORS
    bsl::ostream& logStream();
        // Return the stream for logging.

  public:
    // CREATORS
    explicit Encoder(bslma::Allocator *basicAllocator = 0);
        // Create a encoder object.  Optionally specify a 'basicAllocator' used
        // to supply memory.  If 'basicAllocator' is 0, the currently installed
        // default allocator is used.

    //! ~Encoder() = default;
        // Destroy this object.

    // MANIPULATORS
    template <class TYPE>
    int encode(bsl::streambuf        *streamBuf,
               const TYPE&            value,
               const EncoderOptions&  options);
    template <class TYPE>
    int encode(bsl::streambuf        *streamBuf,
               const TYPE&            value,
               const EncoderOptions  *options);
        // Encode the specified 'value', of (template parameter) 'TYPE', in the
        // JSON format using the specified 'options' and output
        // it onto the specified 'streamBuf'.  Specifying a nullptr 'options'
        // is equivalent to passing a default-constructed DecoderOptions in
        // 'options'.  'TYPE' shall be a 'bdlat'-compatible sequence, choice,
        // or array type, or a 'bdlat'-compatible dynamic type referring to one
        // of those types.  Return 0 on success, and a non-zero value
        // otherwise.

    template <class TYPE>
    int encode(bsl::ostream&         stream,
               const TYPE&           value,
               const EncoderOptions& options);
    template <class TYPE>
    int encode(bsl::ostream&         stream,
               const TYPE&           value,
               const EncoderOptions *options);
        // Encode the specified 'value', of (template parameter) 'TYPE', in the
        // JSON format using the specified 'options' and output
        // it onto the specified 'stream'.  Specifying a nullptr 'options' is
        // equivalent to passing a default-constructed DecoderOptions in
        // 'options'.  'TYPE' shall be a 'bdlat'-compatible choice, or array
        // type, or a 'bdlat'-compatible dynamic type referring to one of those
        // types.  Return 0 on success, and a non-zero value otherwise.

    template <class TYPE>
    int encode(bsl::streambuf *streamBuf, const TYPE& value);
        // Encode the specified 'value' of (template parameter) 'TYPE' into the
        // specified 'streamBuf'.  Return 0 on success, and a non-zero value
        // otherwise.
        //
        // DEPRECATED: Use the 'encode' function passed a reference to a
        // non-modifiable 'EncoderOptions' object instead.

    template <class TYPE>
    int encode(bsl::ostream& stream, const TYPE& value);
        // Encode the specified 'value' of (template parameter) 'TYPE' into the
        // specified 'stream'.  Return 0 on success, and a non-zero value
        // otherwise.  Note that 'stream' will be invalidated if the encoding
        // failed.
        //
        // DEPRECATED: Use the 'encode' function passed a reference to a
        // non-modifiable 'EncoderOptions' object instead.

    // ACCESSORS
    bsl::string loggedMessages() const;
        // Return a string containing any error, warning, or trace messages
        // that were logged during the last call to the 'encode' method.  The
        // log is reset each time 'encode' is called.
};

                        // =============================
                        // struct Encoder_EncodeImplUtil
                        // =============================

struct Encoder_EncodeImplUtil {
    // This component-private utility 'struct' provides a suite of functions
    // that encode 'bdlat' types in the JSON format.

    // TYPES
    typedef Encoder_EncodeImplUtil ThisUtil;
        // 'ThisUtil' is a convenience alias for this utility 'struct'.

    typedef int FormattingMode;
        // 'FormattingMode' is an alias to the type of an 'int'-valued
        // 'bdlat_FormattingMode' bit-field.  A 'FormattingMode' value is not
        // valid unless it is equal to an enumerator of 'bdlat_FormattingMode'
        // or a valid bitwise-or of two or more such enumerators.  See the
        // component-level documentation of {'bdlat_formattingmode'} for a
        // description of the set of valid formatting-mode values.

    // CLASS METHODS

                              // Document Encoding

    static void openDocument(bsl::ostream          *outputStream,
                             const EncoderOptions&  options);
        // Print the sequence of characters that designate the start of a JSON
        // document to the specified 'outputStream' according to the specified
        // encoding 'options'.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the start of a JSON document.

    static void closeDocument(bsl::ostream          *outputStream,
                              const EncoderOptions&  options);
        // Print the sequence of characters that designate the end of a JSON
        // document to the specified 'outputStream' according to the specified
        // encoding 'options'.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the end of a JSON document.

                               // Value Encoding

    template <class TYPE>
    static int encode(bsl::ostream          *jsonStream,
                      const TYPE&            value,
                      const EncoderOptions&  options = EncoderOptions());
        // Encode the JSON representation of the specified 'value' to the
        // specified 'jsonStream'.  Optionally specify 'options' to configure
        // aspects of the JSON representation of the 'value'.  If 'options' is
        // not specified, the default 'EncoderOptions' value is used.  Return 0
        // on success, and a non-zero value otherwise.  The behavior is
        // undefined unless the specified 'TYPE' satisfies both the static and
        // dynamic requirements of one 'bdlat' type-category concept.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'value'.  See the package-level
        // documentation of {'bdlat'} for a description of the available
        // type-category concepts.

    template <class TYPE>
    static int encode(
                     bsl::ostream          *logStream,
                     bsl::ostream          *jsonStream,
                     const TYPE&            value,
                     const EncoderOptions&  options = EncoderOptions());
        // Encode the JSON representation of the specified 'value' to the
        // specified 'jsonStream'.  If this operation is not successful, load
        // an unspecified, human-readable description of the error condition to
        // the specified 'logStream'.  Optionally specify 'options' to
        // configure aspects of the JSON representation of the 'value'.  If
        // 'options' is not specified, the default 'EncoderOptions' value is
        // used.  Return 0 on success, and a non-zero value otherwise.  The
        // behavior is undefined unless the specified 'TYPE' satisfies both the
        // static and dynamic requirements of one 'bdlat' type-category
        // concept.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the JSON representation of the
        // 'value'.  See the package-level documentation of {'bdlat'} for an
        // introduction to the requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    static int encode(bool                  *valueIsEmpty,
                      Formatter             *formatter,
                      bsl::ostream          *logStream,
                      const TYPE&            value,
                      FormattingMode         formattingMode,
                      const EncoderOptions&  options,
                      bool                   isFirstMember);
        // Encode the JSON representation of the specified 'value' to the
        // specified JSON 'formatter', according to the specified
        // 'formattingMode'.  If the representation contains no text, load the
        // value 'true' into 'valueIsEmpty' and the value 'false' otherwise.
        // If the specified 'isFirstMember' option is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter if the remaining representation is
        // non-empty otherwise.  Use the specified 'options' to configure
        // aspects of the JSON representation of the 'value'.  If this
        // operation is not successful, load an unspecified, human-readable
        // description of the error condition to the specified 'logStream'.
        // Return 0 on success, and a non-zero value otherwise.  The behavior
        // is undefined unless the specified 'TYPE' satisfies both the static
        // and dynamic requirements of one of the 'bdlat' type-category
        // concepts.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the JSON representation of the
        // 'value'.  See the package-level documentation of {'bdlat'} for an
        // introduction to the requirements of 'bdlat' type-category concepts.

                                 // Validation

    template <class TYPE>
    static int validate(bsl::ostream               *logStream,
                        const TYPE&                 value,
                        bdlat_TypeCategory::Choice  category);
    template <class TYPE, class CATEGORY>
    static int validate(bsl::ostream *logStream,
                        const TYPE&   value,
                        CATEGORY      category);
        // Determine if the specified 'value' having the specified 'bdlat'
        // 'category' satisfies the requirements for encoding using this
        // component.  If the 'value' meets the encoding requirements, return
        // 0, otherwise load an unspecified, human-readable description of the
        // requirements that are not satisfied by the 'value' and return a
        // non-zero value.  For values satisfying the 'bdlat' 'Choice'
        // type-category concept, the value of the  'selectionId' attribute
        // must not be undefined.  For values satisfying the requirements of
        // other 'bdlat' type-category concepts, there are no further
        // requirements for encoding using this component.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    static int validateChoice(bsl::ostream *logStream, const TYPE& value);
        // Determine if the specified 'value' satisfies the requirements for
        // encoding using this component.  If the 'value' meets the encoding
        // requirements, return 0, otherwise load an unspecified,
        // human-readable description of the requirements that are not
        // satisfied by the 'value' to the specified 'logStream' and return a
        // non-zero value.  The 'value' is required to not have an undefined
        // 'selectionId'.

             // Encoding Values That Have Specific Type Categories

    static int encodeCharArray(Formatter                *formatter,
                               const bsl::vector<char>&  value,
                               const EncoderOptions&     options);
        // Encode the JSON representation of the specified 'value' to the
        // specified JSON 'formatter'.  Use the specified 'options' to
        // configure aspects of the JSON representation of the 'value'.  Return
        // 0 on success, and a non-zero value otherwise.

    template <class TYPE>
    static int encodeSimpleValue(Formatter             *formatter,
                                 const TYPE&            value,
                                 const EncoderOptions&  options);
        // Encode the JSON representation of the specified 'value' to the
        // specified JSON 'formatter'.  Use the specified 'options' to
        // configure aspects of the JSON representation of the 'value'.  Return
        // 0 on success, and a non-zero value otherwise.  The behavior is
        // undefined unless the specified 'TYPE' satisfies both the static and
        // dynamic requirements of the 'Simple' 'bdlat' type-category concept.
        // See the component-level documentation of {'baljsn_encoderoptions'}
        // for a description of the effects, if any, of each option in the
        // 'options' on the JSON representation of the 'value'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

                       // Encoding Prefixes and Suffixes

    static void encodeObjectPrefix(bool           *prefixIsEmpty,
                                   Formatter      *formatter,
                                   FormattingMode  formattingMode);
        // If the specified 'formattingMode' does not have the
        // 'bdlat_FormattingMode::e_UNTAGGED' bit set, encode a "left brace"
        // JSON token to the specified 'formatter', and encoding nothing to the
        // 'formatter' otherwise.  If this operation encodes a token to the
        // formatter, load the value 'false' to the specified 'prefixIsEmpty',
        // and the value 'true' otherwise.

    static void encodeObjectSuffix(bool           *suffixIsEmpty,
                                   Formatter      *formatter,
                                   FormattingMode  formattingMode);
        // If the specified 'formattingMode' does not have the
        // 'bdlat_FormattingMode::e_UNTAGGED' bit set, encode a "right brace"
        // JSON token to the specified 'formatter', and encoding nothing to the
        // 'formatter' otherwise.  If this operation encodes a token to the
        // 'formatter', load the value 'false' to the specified
        // 'suffixIsEmpty', and the value 'true' otherwise.

                  // Encoding Arrays That Have Specific Shapes

    static void encodeEmptyArray(Formatter *formatter);
        // Encode the representation of the empty-array JSON value to the
        // specified 'formatter'.

    template <class TYPE>
    static int encodeNonEmptyArray(Formatter             *formatter,
                                   bsl::ostream          *logStream,
                                   const TYPE&            value,
                                   const EncoderOptions&  options);
        // Encode the JSON representation of the specified 'value' to the
        // specified JSON 'formatter'.  Use the specified 'options' to
        // configure aspects of the JSON representation of the 'value'.  If
        // this operation is not successful, load an unspecified,
        // human-readable description of the error condition to the specified
        // 'logStream'.  Return 0 on success, and a non-zero value otherwise.
        // The behavior is undefined unless the 'value' is non-empty and the
        // specified 'TYPE' satisfies both the static and dynamic requirements
        // of the 'Array' 'bdlat' type-category concept.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'selection'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

                        // Encoding Generalized Members

    static int encodeMember(bool                      *memberIsEmpty,
                            Formatter                 *formatter,
                            bsl::ostream              *logStream,
                            const bslstl::StringRef&   memberName,
                            const bsl::vector<char>&   member,
                            FormattingMode             formattingMode,
                            const EncoderOptions&      options,
                            bool                       isFirstMember,
                            bdlat_TypeCategory::Array  category);
    template <class TYPE>
    static int encodeMember(bool                      *memberIsEmpty,
                            Formatter                 *formatter,
                            bsl::ostream              *logStream,
                            const bslstl::StringRef&   memberName,
                            const TYPE&                member,
                            FormattingMode             formattingMode,
                            const EncoderOptions&      options,
                            bool                       isFirstMember,
                            bdlat_TypeCategory::Array  category);
    template <class TYPE, class OTHER_CATEGORY>
    static int encodeMember(bool                     *memberIsEmpty,
                            Formatter                *formatter,
                            bsl::ostream             *logStream,
                            const bslstl::StringRef&  memberName,
                            const TYPE&               member,
                            FormattingMode            formattingMode,
                            const EncoderOptions&     options,
                            bool                      isFirstMember,
                            OTHER_CATEGORY            category);
        // Encode the JSON representation of the specified object 'member'
        // having the specified 'memberName' to the specified JSON 'formatter',
        // according to the specified 'formattingMode'.  If the representation
        // contains no text, load the value 'true' to 'memberIsEmpty' and the
        // value 'false' otherwise.  If the specified 'isFirstMember' option is
        // 'true', then the representation of the member contains no leading
        // sequence delimiter, and does contain such a delimiter otherwise.
        // Use the specified 'options' to configure aspects of the JSON
        // representation of the 'member'.  If this operation is not
        // successful, load an unspecified, human-readable description of the
        // error condition to the specified 'logStream'.  Return 0 on success,
        // and a non-zero value otherwise.  The behavior is undefined unless
        // the specified 'TYPE' satisfies both the static and dynamic
        // requirements of the specified 'category' 'bdlat' type-category
        // concept.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the JSON representation of the
        // 'selection'.  See the package-level documentation of {'bdlat'} for
        // an introduction to the requirements of 'bdlat' type-category
        // concepts.

    static int encodeMemberPrefix(Formatter                *formatter,
                                  bsl::ostream             *logStream,
                                  const bslstl::StringRef&  memberName,
                                  bool                      isFirstMember);
    static int encodeMemberPrefix(Formatter                *formatter,
                                  bsl::ostream             *logStream,
                                  const bslstl::StringRef&  memberName,
                                  FormattingMode            formattingMode,
                                  bool                      isFirstMember);
    static int encodeMemberPrefix(bool                     *prefixIsEmpty,
                                  Formatter                *formatter,
                                  bsl::ostream             *logStream,
                                  const bslstl::StringRef&  memberName,
                                  FormattingMode            formattingMode,
                                  bool                      isFirstMember);
        // If the specified 'isFirstMember' flag is 'false', encode a "comma"
        // JSON token to the specified 'formatter', and do not encode a "comma"
        // JSON token otherwise.  If the specified 'formattingMode' does not
        // have the 'bdlat_FormattingMode::e_UNTAGGED' bit set, encode a JSON
        // "string" token having the specified 'memberName' contents, and
        // encode a JSON "colon" token after the string, and do not encode
        // these tokens otherwise.  If this operation is not successful, load
        // an unspecified, human-readable description of the error condition to
        // the specified 'logStream'.  Optionally specify 'prefixIsEmpty'.  If
        // this operation encodes a token to the formatter, load the value
        // 'false' to 'prefixIsEmpty' if specified, and the value 'true'
        // otherwise.  Return 0 on success, and a non-zero value otherwise.
};

                         // ===========================
                         // struct Encoder_ValueVisitor
                         // ===========================

class Encoder_ValueVisitor {
    // this component-private class provides a function object used to encode
    // values that satisfy one of the 'bdlat' type-category concepts.
    //
    // This class's constructor closes over the 'formatter', 'logStream',
    // 'formattingMode', 'options', and 'isFirstMember' parameters that are
    // shared between all encoding operations provided in this component.  The
    // function-call operator of this class provides an overload set that
    // accepts an object that satisfies one of the 'bdlat' type-category
    // concepts, and a 'bdlat_TypeCategory' tag type that corresponds to the
    // object's 'bdlat' type category.  Each function-call-operator overload
    // encodes a JSON representation of the specified value to the 'formatter'
    // supplied on construction.
    //
    // Note that objects of this class satisfy the requirements of a 'bdlat'
    // 'Accessor' function object, and may be arguments to functions that take
    // such accessors.

    // TYPES
    typedef int FormattingMode;
        // 'FormattingMode' is an alias to the type of an 'int'-valued
        // 'bdlat_FormattingMode' bit-field.  A 'FormattingMode' value is not
        // valid unless it is equal to an enumerator of 'bdlat_FormattingMode'
        // or a valid bitwise-or of two or more such enumerators.  See the
        // component-level documentation of {'bdlat_formattingmode'} for a
        // description of the set of valid formatting-mode values.

    // DATA
    bool                  d_valueIsEmpty;
        // 'true' after invocation if the empty string represents the encoded
        // value

    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    FormattingMode        d_formattingMode;
        // 'bdlat_FormattingMode' value associated with an attribute,
        // selection, or enumerator, and that controls some aspects of the
        // token sequence to emit

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_ValueVisitor(Formatter             *formatter,
                         bsl::ostream          *logStream,
                         FormattingMode         formattingMode,
                         const EncoderOptions&  options,
                         bool                   isFirstMember);
        // Construct an 'Encoder_ValueVisitor' object having the specified
        // 'formatter', 'logStream', 'formattingMode', 'options', and
        // 'isFirstMember' attributes, and having a 'valueIsEmpty' attribute
        // with the 'false' value.

    // MANIPULATORS
    template <class TYPE>
    int operator()(const TYPE& value);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter if the remaining representation is
        // non-empty otherwise.  The 'options' attribute of this object
        // configures aspects of the JSON representation of the 'value'.  If
        // this operation is not successful, load an unspecified,
        // human-readable description of the error condition to the 'logStream'
        // attribute of this object.  Return 0 on success, and a non-zero value
        // otherwise.  The behavior is undefined unless the specified 'TYPE'
        // satisfies both the static and dynamic requirements of one of the
        // 'bdlat' type-category concepts.  See the component-level
        // documentation of {'baljsn_encoderoptions'} for a description of the
        // effects, if any, of each option in the 'options' on the JSON
        // representation of the 'value'.  See the package-level documentation
        // of {'bdlat'} for an introduction to the requirements of 'bdlat'
        // type-category concepts.

    // ACCESSORS
    bool valueIsEmpty() const;
        // Return the value of the 'valueIsEmpty' attribute of this object.
};

                       // ==============================
                       // struct Encoder_ValueDispatcher
                       // ==============================

class Encoder_ValueDispatcher {
    // this component-private class provides a function object used to encode
    // values that satisfy one of the 'bdlat' type-category concepts.
    //
    // This class's constructor closes over the 'formatter', 'logStream',
    // 'formattingMode', 'options', and 'isFirstMember' parameters that are
    // shared between all encoding operations provided in this component.  The
    // function-call operator of this class provides an overload set that
    // accepts an object that satisfies one of the 'bdlat' type-category
    // concepts, and a 'bdlat_TypeCategory' tag type that corresponds to the
    // object's 'bdlat' type category.  Each function-call-operator overload
    // encodes a JSON representation of the specified value to the 'formatter'
    // supplied on construction.
    //
    // Note that objects of this class satisfy the requirements of a
    // function-object argument to the
    // 'bdlat_TypeCategoryUtil::accessByCategory' function.

    // TYPES
    typedef int FormattingMode;
        // 'FormattingMode' is an alias to the type of an 'int'-valued
        // 'bdlat_FormattingMode' bit-field.  A 'FormattingMode' value is not
        // valid unless it is equal to an enumerator of 'bdlat_FormattingMode'
        // or a valid bitwise-or of two or more such enumerators.  See the
        // component-level documentation of {'bdlat_formattingmode'} for a
        // description of the set of valid formatting-mode values.

    // DATA
    bool                  d_valueIsEmpty;
        // 'true' after invocation if the empty string represents the encoded
        // value

    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    int                   d_formattingMode;
        // 'bdlat_FormattingMode' value associated with an attribute,
        // selection, or enumerator, and that controls some aspects of the
        // token sequence to emit

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_ValueDispatcher(Formatter             *formatter,
                            bsl::ostream          *logStream,
                            FormattingMode         formattingMode,
                            const EncoderOptions&  options,
                            bool                   isFirstMember);
        // Construct an 'Encoder_ValueDispatcher' object having the specified
        // 'formatter', 'logStream', 'formattingMode', 'options', and
        // 'isFirstMember' attributes, and having a 'valueIsEmpty' attribute
        // with the 'false' value.

    // MANIPULATORS
    int operator()(const bsl::vector<char>& value, bdlat_TypeCategory::Array);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object.  The 'options' attribute of
        // this object configures aspects of the JSON representation of the
        // 'value'.  Return 0 on success, and a non-zero value otherwise.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Array);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  The
        // 'options' attribute of this object configures aspects of the JSON
        // representation of the 'value'.  If this operation is not successful,
        // load an unspecified, human-readable description of the error
        // condition to the 'logStream' attribute of this object.  Return 0 on
        // success, and a non-zero value otherwise.  The behavior is undefined
        // unless the specified 'TYPE' satisfies both the static and dynamic
        // requirements of the 'Array' 'bdlat' type-category concept.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'value'.  See the package-level
        // documentation of {'bdlat'} for an introduction to the requirements
        // of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Choice);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter otherwise.  The 'options'
        // attribute of this object configures aspects of the JSON
        // representation of the 'value'.  If this operation is not successful,
        // load an unspecified, human-readable description of the error
        // condition to the 'logStream' attribute of this object.  Return 0 on
        // success, and a non-zero value otherwise.  The behavior is undefined
        // unless the specified 'TYPE' satisfies both the static and dynamic
        // requirements of the 'Choice' 'bdlat' type-category concept.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'value'.  See the package-level
        // documentation of {'bdlat'} for an introduction to the requirements
        // of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::CustomizedType);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter otherwise.  The 'options'
        // attribute of this object configures aspects of the JSON
        // representation of the 'value'.  If this operation is not successful,
        // load an unspecified, human-readable description of the error
        // condition to the specified 'logStream'.  Return 0 on success, and a
        // non-zero value otherwise.  The behavior is undefined unless the
        // specified 'TYPE' satisfies both the static and dynamic requirements
        // of the 'CustomizedType' 'bdlat' type-category concept.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'value'.  See the package-level
        // documentation of {'bdlat'} for an introduction to the requirements
        // of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Enumeration);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object.  The 'options' attribute of
        // this object configures aspects of the JSON representation of the
        // 'value'.  Return 0 on success, and a non-zero value otherwise.  The
        // behavior is undefined unless the specified 'TYPE' satisfies both the
        // static and dynamic requirements of the 'Enumeration' 'bdlat'
        // type-category concept.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the JSON representation of the
        // 'value'.  See the package-level documentation of {'bdlat'} for an
        // introduction to the requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::NullableValue);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter otherwise.  The 'options'
        // attribute of this object configures aspects of the JSON
        // representation of the 'value'.  If this operation is not successful,
        // load an unspecified, human-readable description of the error
        // condition to the 'logStream' attribute of this object.  Return 0 on
        // success, and a non-zero value otherwise.  The behavior is undefined
        // unless the specified 'TYPE' satisfies both the static and dynamic
        // requirements of the 'NullableValue' 'bdlat' type-category concept.
        // See the component-level documentation of {'baljsn_encoderoptions'}
        // for a description of the effects, if any, of each option in the
        // 'options' on the JSON representation of the 'value'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Sequence);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'valueIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the value contains no leading sequence delimiter,
        // and does contain such a delimiter otherwise.  The 'options'
        // attribute of this object configures aspects of the JSON
        // representation of the 'value'.  If this operation is not successful,
        // load an unspecified, human-readable description of the error
        // condition to the 'logStream' attribute of this object.  Return 0 on
        // success, and a non-zero value otherwise.  The behavior is undefined
        // unless the specified 'TYPE' satisfies both the static and dynamic
        // requirements of the 'Sequence' 'bdlat' type-category concept.  See
        // the component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'value'.  See the package-level
        // documentation of {'bdlat'} for an introduction to the requirements
        // of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE& value, bdlat_TypeCategory::Simple);
        // Encode the JSON representation of the specified 'value' to the JSON
        // 'formatter' attribute of this object.  The 'options' attribute of
        // this object configures aspects of the JSON representation of the
        // 'value'.  Return 0 on success, and a non-zero value otherwise.  The
        // behavior is undefined unless the specified 'TYPE' satisfies both the
        // static and dynamic requirements of the 'Simple' 'bdlat'
        // type-category concept.  See the component-level documentation of
        // {'baljsn_encoderoptions'} for a description of the effects, if any,
        // of each option in the 'options' on the JSON representation of the
        // 'value'.  See the package-level documentation of {'bdlat'} for an
        // introduction to the requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);
        // The behavior of this function is undefined.

    // ACCESSORS
    bool valueIsEmpty() const;
        // Return the value of the 'valueIsEmpty' attribute of this object.
};


                       // ==============================
                       // class Encoder_SelectionVisitor
                       // ==============================

class Encoder_SelectionVisitor {
    // This component-private class provides a function object used to encode
    // 'bdlat' choice selection values.
    //
    // This class's constructor  closes over the 'formatter', 'logStream',
    // 'options', and 'isFirstMember' parameters that are shared between all
    // encoding operations provided in this component.  The function-call
    // operator of this class provides an overload set that accepts a
    // selection object that satisfies one of the 'bdlat' type-category
    // concepts, and a selection-info object used to provide metadata for the
    // selection.  Each function-call-operator overload encodes a JSON
    // representation of the specified selection to the 'formatter' supplied on
    // construction.
    //
    // Note that objects of this class satisfy the requirements of a
    // function-object argument to the
    // 'bdlat_ChoiceFunctions::accessSelection' function.

    // DATA
    bool                  d_selectionIsEmpty;
        // 'true' after invocation if the empty string represents the encoded
        // value

    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_SelectionVisitor(Formatter             *formatter,
                             bsl::ostream          *logStream,
                             const EncoderOptions&  options,
                             bool                   isFirstMember);
        // Construct an 'Encoder_SelectionVisitor' object having the specified
        // 'formatter', 'logStream', 'options', and 'isFirstMember' attributes,
        // and having a 'selectionIsEmpty' attribute with the 'false' value.

    // MANIPULATORS
    template <class TYPE, class INFO>
    int operator()(const TYPE& selection, const INFO& selectionInfo);
        // Encode the JSON representation of the specified 'selection' having
        // the 'name' from the specified 'selectionInfo' to the JSON
        // 'formatter' attribute of this object according to the
        // 'formattingMode' of the 'selectionInfo'.  If the representation
        // contains no text, load the value 'true' into the 'selectionIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the selection contains no leading sequence
        // delimiter, and does contain such a delimiter otherwise.  The
        // 'options' attribute of this object configures aspects of the JSON
        // representation of the 'selection'.  If this operation is not
        // successful, load an unspecified, human-readable description of the
        // error condition to the 'logStream' attribute of this object.  Return
        // 0 on success, and a non-zero value otherwise.  The behavior is
        // undefined unless the 'selection' satisfies both the static and
        // dynamic requirements of one of the 'bdlat' type-category concepts.
        // See the component-level documentation of {'baljsn_encoderoptions'}
        // for a description of the effects, if any, of each option in the
        // 'options' on the JSON representation of the 'selection'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

    // ACCESSORS
    bool selectionIsEmpty() const;
        // Return the value of the 'selectionIsEmpty' attribute of this object.
};

                      // =================================
                      // class Encoder_SelectionDispatcher
                      // =================================

class Encoder_SelectionDispatcher {
    // This component-private class provides a function object that closes over
    // the 'formatter', 'logStream', 'selectionName', 'formattingMode',
    // 'options', and 'isFirstMember' parameters that are shared between all
    // encoding operations provided in this component.  The function-call
    // operator of this class provides an overload set that accepts a
    // "selection" object that satisfies one of the 'bdlat' type-category
    // concepts, and an optional 'bdlat_TypeCategory' tag type that corresponds
    // to the selections's 'bdlat' type category.  Each function-call-operator
    // overload encodes a JSON representation of the specified selection to the
    // 'formatter' supplied on construction.
    //
    // Note that objects of this class satisfy the requirements of a
    // function-object argument to the
    // 'bdlat_TypeCategoryUtil::accessByCategory' function, and a generic
    // 'bdlat' "accessor."

    // TYPES
    typedef int FormattingMode;
        // 'FormattingMode' is an alias to the type of an 'int'-valued
        // 'bdlat_FormattingMode' bit-field.  A 'FormattingMode' value is not
        // valid unless it is equal to an enumerator of 'bdlat_FormattingMode'
        // or a valid bitwise-or of two or more such enumerators.  See the
        // component-level documentation of {'bdlat_formattingmode'} for a
        // description of the set of valid formatting-mode values.

    // DATA
    bool                  d_selectionIsEmpty;
        // 'true' after invocation if the empty string represents the encoded
        // selection

    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    bslstl::StringRef     d_selectionName;
        // uniquely identifies the selection among all selections of one
        // choice

    FormattingMode        d_formattingMode;
        // 'bdlat_FormattingMode' value associated with an attribute,
        // selection, or enumerator, and that controls some aspects of the
        // token sequence to emit

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_SelectionDispatcher(Formatter                *formatter,
                                bsl::ostream             *logStream,
                                const bslstl::StringRef&  selectionName,
                                FormattingMode            formattingMode,
                                const EncoderOptions&     options,
                                bool                      isFirstMember);
        // Construct an 'Encoder_SelectionDispatcher' object having the
        // specified 'formatter', 'logStream', 'selectionName',
        // 'formattingMode', 'options', and 'isFirstMember' attributes, and
        // having a 'selectionIsEmpty' attribute with the 'false' value.

    // MANIPULATORS
    template <class TYPE>
    int operator()(const TYPE& selection);
    template <class TYPE>
    int operator()(const TYPE&                        selection,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE, class CATEGORY>
    int operator()(const TYPE& selection, CATEGORY category);
        // Encode the JSON representation of the specified 'selection', having
        // the name equal to the 'selectionName' attribute of this object, to
        // the JSON 'formatter' attribute of this object, according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'selectionIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the selection contains no leading sequence
        // delimiter, and does contain such a delimiter otherwise.  The
        // 'options' attribute of this object configures aspects of the JSON
        // representation of the 'selection'.  If this operation is not
        // successful, load an unspecified, human-readable description of the
        // error condition to the 'logStream' attribute of this object.  Return
        // 0 on success, and a non-zero value otherwise.  The behavior is
        // undefined unless the 'selection' satisfies both the static and
        // dynamic requirements of the 'bdlat' type-category concept
        // corresponding to the 'category', if specified.  See the
        // component-level documentation of {'baljsn_encoderoptions'} for a
        // description of the effects, if any, of each option in the 'options'
        // on the JSON representation of the 'selection'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);
        // The behavior of this function is undefined.

    // ACCESSORS
    bool selectionIsEmpty() const;
        // Return the value of the 'selectionIsEmpty' attribute of this object.
};

                       // ==============================
                       // class Encoder_AttributeVisitor
                       // ==============================

class Encoder_AttributeVisitor {
    // This component-private class provides a function object used to encode
    // 'bdlat' sequence attribute values.
    //
    // This class's constructor  closes over the 'formatter', 'logStream',
    // 'options', and 'isFirstMember' parameters that are shared between all
    // encoding operations provided in this component.  The function-call
    // operator of this class provides an overload set that accepts an
    // attribute object that satisfies one of the 'bdlat' type-category
    // concepts, and an attribute-info object used to provide metadata for the
    // attribute.  Each function-call-operator overload encodes a JSON
    // representation of the specified attribute to the 'formatter' supplied on
    // construction.
    //
    // Note that objects of this class satisfy the requirements of a
    // function-object argument to the
    // 'bdlat_SequenceFunctions::accessAttributes' function.

    // DATA
    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_AttributeVisitor(Formatter             *formatter,
                             bsl::ostream          *logStream,
                             const EncoderOptions&  options,
                             bool                   isFirstMember);
        // Construct an 'Encoder_AttributeVisitor' object having the specified
        // 'formatter', 'logStream', 'options', and 'isFirstMember'
        // attributes, and having an 'attributesAreEmpty' attribute with the
        // 'false' value.

    // MANIPULATORS
    template <class TYPE, class INFO>
    int operator()(const TYPE& attribute, const INFO &attributeInfo);
        // Encode the JSON representation of the specified 'attribute' having
        // the 'name' from the specified 'attributeInfo' to the JSON
        // 'formatter' attribute of this object according to the
        // 'formattingMode' of the 'attributeInfo'.  If the representation
        // contains no text, load the value 'true' into the 'attributeIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the attribute contains no leading sequence
        // delimiter, and does contain such a delimiter otherwise.  The
        // 'options' attribute of this object configures aspects of the JSON
        // representation of the 'attribute'.  If this operation is not
        // successful, load an unspecified, human-readable description of the
        // error condition to the 'logStream' attribute of this object.  Return
        // 0 on success, and a non-zero value otherwise.  The behavior is
        // undefined unless the 'attribute' satisfies both the static and
        // dynamic requirements of one of the 'bdlat' type-category concepts.
        // See the component-level documentation of {'baljsn_encoderoptions'}
        // for a description of the effects, if any, of each option in the
        // 'options' on the JSON representation of the 'attribute'.  See the
        // package-level documentation of {'bdlat'} for an introduction to the
        // requirements of 'bdlat' type-category concepts.

    // ACCESSORS
    bool attributesAreEmpty() const;
        // Return the value of the 'attributesAreEmpty' attribute of this
        // object.
};

                      // =================================
                      // class Encoder_AttributeDispatcher
                      // =================================

class Encoder_AttributeDispatcher {
    // This component-private class provides a function object that closes over
    // the 'formatter', 'logStream', 'attributeName', 'formattingMode',
    // 'options', and 'isFirstMember' parameters that are shared between all
    // encoding operations provided in this component.  The function-call
    // operator of this class provides an overload set that accepts an
    // "attribute" object that satisfies one of the 'bdlat' type-category
    // concepts, and an optional 'bdlat_TypeCategory' tag type that corresponds
    // to the attribute's 'bdlat' type category.  Each function-call-operator
    // overload encodes a JSON representation of the specified attribute to the
    // 'formatter' supplied on construction.
    //
    // Note that objects of this class satisfy the requirements of a
    // function-object argument to the
    // 'bdlat_TypeCategoryUtil::accessByCategory' function, and a generic
    // 'bdlat' "accessor."

    // TYPES
    typedef int FormattingMode;
        // 'FormattingMode' is an alias to the type of an 'int'-valued
        // 'bdlat_FormattingMode' bit-field.  A 'FormattingMode' value is not
        // valid unless it is equal to an enumerator of 'bdlat_FormattingMode'
        // or a valid bitwise-or of two or more such enumerators.  See the
        // component-level documentation of {'bdlat_formattingmode'} for a
        // description of the set of valid formatting-mode values.

    // DATA
    bool                  d_attributeIsEmpty;
        // 'true' after invocation if the JSON representation of the attribute
        // contains no tokens, and 'false' otherwise (if it does contain
        // tokens)

    Formatter            *d_formatter_p;
        // wrapper around the output stream that determines the whitespace to
        // emit around each JSON token

    bsl::ostream         *d_logStream_p;
        // human-readable descriptions of all encountered error conditions

    bslstl::StringRef     d_attributeName;
        // uniquely identifies the attribute among all attributes of one
        // sequence

    FormattingMode        d_formattingMode;
        // 'bdlat_FormattingMode' value associated with an attribute,
        // selection, or enumerator, and that controls some aspects of the
        // token sequence to emit

    const EncoderOptions *d_options_p;
        // options set by the caller of the encoding operation that controls
        // some aspects of the token sequence to emit

    bool                  d_isFirstMember;
        // flag that indicates if the leading delimiter should be omitted if
        // 'true', and preserved otherwise

  public:
    // CREATORS
    Encoder_AttributeDispatcher(Formatter                *formatter,
                                bsl::ostream             *logStream,
                                const bslstl::StringRef&  attributeName,
                                FormattingMode            formattingMode,
                                const EncoderOptions&     options,
                                bool                      isFirstMember);
        // Construct an 'Encoder_AttributeDispatcher' object having the
        // specified 'formatter', 'logStream', 'attributeName', 'mode',
        // 'options', and 'isFirstMember' attributes, and having an
        // 'attributeIsEmpty' attribute with the 'false' value.

    // MANIPULATORS
    template <class TYPE>
    int operator()(const TYPE& attribute);
    int operator()(const bsl::vector<char>&  attribute,
                   bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE& attribute, bdlat_TypeCategory::Array category);
    template <class TYPE>
    int operator()(const TYPE&                        attribute,
                   bdlat_TypeCategory::CustomizedType category);
    template <class TYPE>
    int operator()(const TYPE&                       attribute,
                   bdlat_TypeCategory::NullableValue category);
    template <class TYPE, class CATEGORY>
    int operator()(const TYPE& attribute, CATEGORY category);
        // Encode the JSON representation of the specified 'attribute', having
        // the name equal to the 'attributeName' attribute of this object, to
        // the JSON 'formatter' attribute of this object according to the
        // 'formattingMode' attribute of this object.  If the representation
        // contains no text, load the value 'true' into the 'attributeIsEmpty'
        // attribute of this object, and the value 'false' otherwise.  If the
        // 'isFirstMember' attribute of this object is 'true', then the
        // representation of the attribute contains no leading sequence
        // delimiter, and does contain such a delimiter otherwise.  The
        // 'options' attribute of this object configures aspects of the JSON
        // representation of the 'attribute'.  If this operation is not
        // successful, load an unspecified, human-readable description of the
        // error condition to the 'logStream' attribute of this object.  Return
        // 0 on success, and a non-zero value otherwise.  Optionally specify a
        // 'category' 'bdlat' type-category tag.  The behavior is undefined
        // unless the 'attribute' satisfies both the static and dynamic
        // requirements of the 'bdlat' type-category concept corresponding to
        // the 'category', if specified.  See the component-level documentation
        // of {'baljsn_encoderoptions'} for a description of the effects, if
        // any, of each option in the 'options' on the JSON representation of
        // the 'attribute'.  See the package-level documentation of {'bdlat'}
        // for an introduction to the requirements of 'bdlat' type-category
        // concepts.

    template <class TYPE>
    int operator()(const TYPE&, bslmf::Nil);
        // The behavior of this function is undefined.

    // ACCESSORS
    bool attributeIsEmpty() const;
        // Return the value of the 'attributeIsEmpty' attribute of this object.
};

// ============================================================================
//                            INLINE DEFINITIONS
// ============================================================================

                               // -------------
                               // class Encoder
                               // -------------

// PRIVATE MANIPULATORS
inline
bsl::ostream& Encoder::logStream()
{
    return d_logStream;
}

// CREATORS
inline
Encoder::Encoder(bslma::Allocator *basicAllocator)
: d_logStream(basicAllocator)
{
}

// MANIPULATORS
template <class TYPE>
inline
int Encoder::encode(bsl::streambuf *streamBuf, const TYPE& value)
{
    const EncoderOptions options;
    return encode(streamBuf, value, options);
}

template <class TYPE>
inline
int Encoder::encode(bsl::ostream& stream, const TYPE& value)
{
    const EncoderOptions options;
    return encode(stream, value, options);
}

template <class TYPE>
int Encoder::encode(bsl::streambuf        *streamBuf,
                    const TYPE&            value,
                    const EncoderOptions&  options)
{
    BSLS_ASSERT(streamBuf);

    d_logStream.clear();
    d_logStream.str("");

    bdlat_TypeCategory::Value category =
                                    bdlat_TypeCategoryFunctions::select(value);
    if (bdlat_TypeCategory::e_SEQUENCE_CATEGORY != category
     && bdlat_TypeCategory::e_CHOICE_CATEGORY != category
     && bdlat_TypeCategory::e_ARRAY_CATEGORY != category) {
        logStream()
            << "Encoded object must be a Sequence, Choice, or Array type."
            << bsl::endl;
        return -1;                                                    // RETURN
    }

    bsl::ostream outputStream(streamBuf);
    Encoder_EncodeImplUtil::openDocument(&outputStream, options);

    const int rc = Encoder_EncodeImplUtil::encode(
        &d_logStream, &outputStream, value, options);
    if (0 != rc) {
        streamBuf->pubsync();
        return rc;                                                    // RETURN
    }

    Encoder_EncodeImplUtil::closeDocument(&outputStream, options);

    if (!outputStream) {
        logStream()
            << "An error occurred when writing to the supplied output stream"
               " or stream buffer."
            << bsl::endl;
        streamBuf->pubsync();
        return -1;                                                    // RETURN
    }

    streamBuf->pubsync();

    return 0;
}

template <class TYPE>
int Encoder::encode(bsl::streambuf        *streamBuf,
                    const TYPE&            value,
                    const EncoderOptions  *options)
{
    EncoderOptions localOpts;
    return encode(streamBuf, value, options ? *options : localOpts);
}

template <class TYPE>
int Encoder::encode(bsl::ostream&         stream,
                    const TYPE&           value,
                    const EncoderOptions& options)
{
    if (!stream.good()) {
        logStream() << "Invalid stream." << bsl::endl;
        return -1;                                                    // RETURN
    }

    const int rc = this->encode(stream.rdbuf(), value, options);
    if (rc) {
        stream.setstate(bsl::ios_base::failbit);
        return rc;                                                    // RETURN
    }

    return 0;
}

template <class TYPE>
inline
int Encoder::encode(bsl::ostream&         stream,
                    const TYPE&           value,
                    const EncoderOptions *options)
{
    EncoderOptions localOpts;
    return encode(stream, value, options ? *options : localOpts);
}

// ACCESSORS
inline
bsl::string Encoder::loggedMessages() const
{
    return d_logStream.str();
}

                        // -----------------------------
                        // struct Encoder_EncodeImplUtil
                        // -----------------------------

// CLASS METHODS

                         // Document Encoding Functions

inline
void Encoder_EncodeImplUtil::openDocument(bsl::ostream *outputStream,
                                      const EncoderOptions& options)
{
    if (baljsn::EncoderOptions::e_PRETTY ==
                                         options.encodingStyle()) {
        bdlb::Print::indent(*outputStream,
                            options.initialIndentLevel(),
                            options.spacesPerLevel());
    }
}

inline
void Encoder_EncodeImplUtil::closeDocument(bsl::ostream *outputStream,
                                       const baljsn::EncoderOptions& options)
{
    if (baljsn::EncoderOptions::e_PRETTY ==
                                         options.encodingStyle()) {
        (*outputStream) << '\n';
    }
}

                               // Value Encoding

template <class TYPE>
inline
int Encoder_EncodeImplUtil::encode(bsl::ostream          *jsonStream,
                                   const TYPE&            value,
                                   const EncoderOptions&  options)
{
    bdlsb::MemOutStreamBuf logStreamBuf;
    bsl::ostream logStream(&logStreamBuf);

    return encode(&logStream, jsonStream, value, options);
}

template <class TYPE>
inline
int Encoder_EncodeImplUtil::encode(bsl::ostream          *logStream,
                                   bsl::ostream          *jsonStream,
                                   const TYPE&            value,
                                   const EncoderOptions&  options)
{
    static const FormattingMode s_MODE = bdlat_FormattingMode::e_DEFAULT;
    static const bool           s_FIRST_MEMBER_FLAG = false;

    baljsn::Formatter formatter(*jsonStream,
                                baljsn::EncoderOptions::e_PRETTY ==
                                    options.encodingStyle(),
                                options.initialIndentLevel(),
                                options.spacesPerLevel());

    bool valueIsEmpty = false;
    return encode(&valueIsEmpty,
                  &formatter,
                  logStream,
                  value,
                  s_MODE,
                  options,
                  s_FIRST_MEMBER_FLAG);
}

template <class TYPE>
int Encoder_EncodeImplUtil::encode(bool                  *valueIsEmpty,
                                   Formatter             *formatter,
                                   bsl::ostream          *logStream,
                                   const TYPE&            value,
                                   FormattingMode         formattingMode,
                                   const EncoderOptions&  options,
                                   bool                   isFirstMember)
{
    Encoder_ValueDispatcher proxy(formatter,
                                  logStream,
                                  formattingMode,
                                  options,
                                  isFirstMember);

    int rc = bdlat_TypeCategoryUtil::accessByCategory(value, proxy);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    *valueIsEmpty = proxy.valueIsEmpty();
    return 0;
}

                                 // Validation

template <class TYPE>
int Encoder_EncodeImplUtil::validate(bsl::ostream               *logStream,
                                     const TYPE&                 value,
                                     bdlat_TypeCategory::Choice)
{
    return validateChoice(logStream, value);
}

template <class TYPE, class CATEGORY>
inline
int Encoder_EncodeImplUtil::validate(bsl::ostream *, const TYPE&, CATEGORY)
{
    return 0;
}

template <class TYPE>
int Encoder_EncodeImplUtil::validateChoice(bsl::ostream *logStream,
                                           const TYPE&   value)
{
    if (bdlat_ChoiceFunctions::k_UNDEFINED_SELECTION_ID ==
        bdlat_ChoiceFunctions::selectionId(value)) {
        (*logStream) << "Undefined selection for Choice object" << bsl::endl;
        return -1;                                                    // RETURN
    }

    return 0;
}


             // Encoding Values That Have Specific Type Categories

template <class TYPE>
inline
int Encoder_EncodeImplUtil::encodeSimpleValue(Formatter             *formatter,
                                              const TYPE&            value,
                                              const EncoderOptions&  options)
{
    return formatter->putValue(value, &options);
}

                    // Encoding Value Prefixes and Suffixes

inline
void Encoder_EncodeImplUtil::encodeObjectPrefix(
                                               bool           *prefixIsEmpty,
                                               Formatter      *formatter,
                                               FormattingMode  formattingMode)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *prefixIsEmpty = true;
        return;                                                       // RETURN
    }

    formatter->openObject();

    *prefixIsEmpty = false;
}

inline
void Encoder_EncodeImplUtil::encodeObjectSuffix(bool           *suffixIsEmpty,
                                                Formatter      *formatter,
                                                FormattingMode  formattingMode)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *suffixIsEmpty = true;
        return;                                                       // RETURN
    }

    formatter->closeObject();

    *suffixIsEmpty = false;
}

                  // Encoding Arrays That Have Specific Shapes

inline
void Encoder_EncodeImplUtil::encodeEmptyArray(Formatter *formatter)
{
    formatter->openArray(true);
    formatter->closeArray(true);
}

template <class TYPE>
int Encoder_EncodeImplUtil::encodeNonEmptyArray(
                                         Formatter             *formatter,
                                         bsl::ostream          *logStream,
                                         const TYPE&            value,
                                         const EncoderOptions&  options)
{
    const int size = static_cast<int>(bdlat_ArrayFunctions::size(value));
    BSLS_ASSERT(0 < size);

    formatter->openArray();

    static const bool s_FIRST_VALUE_IS_FIRST_ELEMENT = true;
    Encoder_ValueVisitor visitor(formatter,
                                 logStream,
                                 bdlat_FormattingMode::e_DEFAULT,
                                 options,
                                 s_FIRST_VALUE_IS_FIRST_ELEMENT);

    int rc = bdlat_ArrayFunctions::accessElement(value, visitor, 0);
    if (rc) {
        return rc;                                                    // RETURN
    }

    for (int i = 1; i < size; ++i) {
        if (!visitor.valueIsEmpty()) {
            formatter->addArrayElementSeparator();
        }

        rc = bdlat_ArrayFunctions::accessElement(value, visitor, i);
        if (rc) {
            return rc;                                                // RETURN
        }
    }

    formatter->closeArray();

    return 0;
}

                        // Encoding Generalized Members

template <class TYPE>
int Encoder_EncodeImplUtil::encodeMember(
                                      bool                      *memberIsEmpty,
                                      Formatter                 *formatter,
                                      bsl::ostream              *logStream,
                                      const bslstl::StringRef&   memberName,
                                      const TYPE&                member,
                                      FormattingMode             formattingMode,
                                      const EncoderOptions&      options,
                                      bool                       isFirstMember,
                                      bdlat_TypeCategory::Array  category)
    ///Implementation Note
    ///- - - - - - - - - -
    // This function purposefully ignores the 'EncodeEmptyArrays' option in the
    // specified 'options' and always encodes the value of the specified
    // 'member' array.  The caller is responsible for checking the value of
    // this option and deciding whether to obey the option or not.  Callers
    // that encode array-valued attributes of sequences must always obey the
    // option.  Callers that encode array-valued selections of choices must
    // never obey the option, and must always encode the array value.
{
    int rc = ThisUtil::validate(logStream, member, category);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    rc = ThisUtil::encodeMemberPrefix(
        formatter, logStream, memberName, formattingMode, isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    if (bdlat_ArrayFunctions::size(member) == 0) {
        ThisUtil::encodeEmptyArray(formatter);

        *memberIsEmpty = false;
        return 0;                                                     // RETURN
    }

    rc = ThisUtil::encodeNonEmptyArray(formatter, logStream, member, options);
    if (0 != rc) {
        (*logStream) << "Unable to encode value of element "
                     << "named: '" << memberName << "'." << bsl::endl;
        return rc;                                                    // RETURN
    }

    *memberIsEmpty = false;
    return 0;
}

template <class TYPE, class OTHER_CATEGORY>
int Encoder_EncodeImplUtil::encodeMember(
                                       bool                     *memberIsEmpty,
                                       Formatter                *formatter,
                                       bsl::ostream             *logStream,
                                       const bslstl::StringRef&  memberName,
                                       const TYPE&               member,
                                       FormattingMode            formattingMode,
                                       const EncoderOptions&     options,
                                       bool                      isFirstMember,
                                       OTHER_CATEGORY            category)
{
    int rc = ThisUtil::validate(logStream, member, category);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool prefixIsEmpty = false;
    rc = ThisUtil::encodeMemberPrefix(&prefixIsEmpty,
                                      formatter,
                                      logStream,
                                      memberName,
                                      formattingMode,
                                      isFirstMember);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool valueIsEmpty = false;
    rc = ThisUtil::encode(&valueIsEmpty,
                          formatter,
                          logStream,
                          member,
                          formattingMode,
                          options,
                          !prefixIsEmpty || isFirstMember);
    if (0 != rc) {
        (*logStream) << "Unable to encode value of element "
                     << "named: '" << memberName << "'."
                     << bsl::endl;
        return rc;                                                    // RETURN
    }

    BSLS_ASSERT(!valueIsEmpty || prefixIsEmpty);
        // If the value is empty then the prefix is empty.  Otherwise, this
        // function would produce invalid JSON because it would emit a member
        // name token and a colon token, but no member value.

    *memberIsEmpty = valueIsEmpty;
    return 0;
}

inline
int Encoder_EncodeImplUtil::encodeMemberPrefix(
                                       Formatter                *formatter,
                                       bsl::ostream             *logStream,
                                       const bslstl::StringRef&  memberName,
                                       bool                      isFirstMember)
{
    if (!isFirstMember) {
        formatter->closeMember();
    }

    int rc = formatter->openMember(memberName);
    if (0 != rc) {
        (*logStream) << "Unable to encode element name: '" << memberName
                     << "'." << bsl::endl;
        return rc;                                                    // RETURN
    }

    return 0;
}

inline
int Encoder_EncodeImplUtil::encodeMemberPrefix(
                                       Formatter                *formatter,
                                       bsl::ostream             *logStream,
                                       const bslstl::StringRef&  memberName,
                                       FormattingMode            formattingMode,
                                       bool                      isFirstMember)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        return 0;                                                     // RETURN
    }

    return ThisUtil::encodeMemberPrefix(
        formatter, logStream, memberName, isFirstMember);
}

inline
int Encoder_EncodeImplUtil::encodeMemberPrefix(
                                       bool                     *prefixIsEmpty,
                                       Formatter                *formatter,
                                       bsl::ostream             *logStream,
                                       const bslstl::StringRef&  memberName,
                                       FormattingMode            formattingMode,
                                       bool                      isFirstMember)
{
    if (bdlat_FormattingMode::e_UNTAGGED & formattingMode) {
        *prefixIsEmpty = true;
        return 0;                                                     // RETURN
    }

    int rc = ThisUtil::encodeMemberPrefix(
        formatter, logStream, memberName, isFirstMember);
    if (0 != rc) {
        return rc;
    }

    *prefixIsEmpty = false;
    return 0;
}

                        // ---------------------------
                        // struct Encoder_ValueVisitor
                        // ---------------------------

// CREATORS
inline
Encoder_ValueVisitor::Encoder_ValueVisitor(
                                         Formatter             *formatter,
                                         bsl::ostream          *logStream,
                                         FormattingMode         formattingMode,
                                         const EncoderOptions&  options,
                                         bool                   isFirstMember)
: d_valueIsEmpty(false)
, d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_formattingMode(formattingMode)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// MANIPULATORS
template <class TYPE>
inline
int Encoder_ValueVisitor::operator()(const TYPE& value)
{
    return Encoder_EncodeImplUtil::encode(&d_valueIsEmpty,
                                          d_formatter_p,
                                          d_logStream_p,
                                          value,
                                          d_formattingMode,
                                          *d_options_p,
                                          d_isFirstMember);
}

// ACCESSORS
inline
bool Encoder_ValueVisitor::valueIsEmpty() const
{
    return d_valueIsEmpty;
}

                       // ------------------------------
                       // struct Encoder_ValueDispatcher
                       // ------------------------------

// CREATORS
inline
Encoder_ValueDispatcher::Encoder_ValueDispatcher(
                                         Formatter             *formatter,
                                         bsl::ostream          *logStream,
                                         FormattingMode         formattingMode,
                                         const EncoderOptions&  options,
                                         bool                   isFirstMember)
: d_valueIsEmpty(false)
, d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_formattingMode(formattingMode)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// MANIPULATORS
inline
int Encoder_ValueDispatcher::operator()(const bsl::vector<char>&  value,
                                        bdlat_TypeCategory::Array)
{
    d_valueIsEmpty = false;
    return Encoder_EncodeImplUtil::encodeCharArray(
        d_formatter_p, value, *d_options_p);
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&               value,
                                        bdlat_TypeCategory::Array)
{
    const bool arrayIsEmpty = (0 == bdlat_ArrayFunctions::size(value));

    if (arrayIsEmpty && !d_options_p->encodeEmptyArrays()) {
        d_valueIsEmpty = true;
        return 0;                                                     // RETURN
    }

    if (arrayIsEmpty && d_options_p->encodeEmptyArrays()) {
        Encoder_EncodeImplUtil::encodeEmptyArray(d_formatter_p);
        d_valueIsEmpty = false;
        return 0;                                                     // RETURN
    }

    int rc = Encoder_EncodeImplUtil::encodeNonEmptyArray(
        d_formatter_p, d_logStream_p, value, *d_options_p);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_valueIsEmpty = false;
    return 0;
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&                value,
                                        bdlat_TypeCategory::Choice)
{
    int rc = Encoder_EncodeImplUtil::validateChoice(d_logStream_p, value);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool prefixIsEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectPrefix(
        &prefixIsEmpty, d_formatter_p, d_formattingMode);

    Encoder_SelectionVisitor visitor(d_formatter_p,
                                     d_logStream_p,
                                     *d_options_p,
                                     !prefixIsEmpty || d_isFirstMember);
    rc = bdlat_ChoiceFunctions::accessSelection(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool suffixIsEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectSuffix(
        &suffixIsEmpty, d_formatter_p, d_formattingMode);

    d_valueIsEmpty =
        prefixIsEmpty && visitor.selectionIsEmpty() && suffixIsEmpty;
    return 0;
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(
                                      const TYPE&                        value,
                                      bdlat_TypeCategory::CustomizedType)
{
    return Encoder_EncodeImplUtil::encode(
        &d_valueIsEmpty,
        d_formatter_p,
        d_logStream_p,
        bdlat_CustomizedTypeFunctions::convertToBaseType(value),
        d_formattingMode,
        *d_options_p,
        d_isFirstMember);
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&                     value,
                                        bdlat_TypeCategory::Enumeration)
{
    bsl::string valueString;
    bdlat_EnumFunctions::toString(&valueString, value);

    d_valueIsEmpty = false;
    return Encoder_EncodeImplUtil::encodeSimpleValue(
        d_formatter_p, valueString, *d_options_p);
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(
                                       const TYPE&                       value,
                                       bdlat_TypeCategory::NullableValue)
{
    if (bdlat_NullableValueFunctions::isNull(value)) {
        d_formatter_p->putNullValue();
        d_valueIsEmpty = false;
        return 0;                                                     // RETURN
    }

    Encoder_ValueVisitor visitor(d_formatter_p,
                                 d_logStream_p,
                                 d_formattingMode,
                                 *d_options_p,
                                 d_isFirstMember);
    int rc = bdlat_NullableValueFunctions::accessValue(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_valueIsEmpty = visitor.valueIsEmpty();
    return 0;
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&                  value,
                                        bdlat_TypeCategory::Sequence)
{
    bool prefixIsEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectPrefix(
        &prefixIsEmpty, d_formatter_p, d_formattingMode);

    Encoder_AttributeVisitor visitor(d_formatter_p,
                                     d_logStream_p,
                                     *d_options_p,
                                     !prefixIsEmpty || d_isFirstMember);
    int rc = bdlat_SequenceFunctions::accessAttributes(value, visitor);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    bool suffixIsEmpty = false;
    Encoder_EncodeImplUtil::encodeObjectSuffix(
        &suffixIsEmpty, d_formatter_p, d_formattingMode);

    d_valueIsEmpty =
        prefixIsEmpty && visitor.attributesAreEmpty() && suffixIsEmpty;
    return 0;
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&                value,
                                        bdlat_TypeCategory::Simple)
{
    d_valueIsEmpty = false;
    return Encoder_EncodeImplUtil::encodeSimpleValue(
        d_formatter_p, value, *d_options_p);
}

template <class TYPE>
inline
int Encoder_ValueDispatcher::operator()(const TYPE&, bslmf::Nil)
{
    BSLS_ASSERT_OPT(!"Unreachable");
    return -1;
}

// ACCESSORS
inline
bool Encoder_ValueDispatcher::valueIsEmpty() const
{
    return d_valueIsEmpty;
}

                       // ------------------------------
                       // class Encoder_SelectionVisitor
                       // ------------------------------

// CREATORS
inline
Encoder_SelectionVisitor::Encoder_SelectionVisitor(
                                         Formatter             *formatter,
                                         bsl::ostream          *logStream,
                                         const EncoderOptions&  options,
                                         bool                   isFirstMember)
: d_selectionIsEmpty(false)
, d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// MANIPULATORS
template <class TYPE, class INFO>
int Encoder_SelectionVisitor::operator()(const TYPE& selection,
                                         const INFO& selectionInfo)
{
    Encoder_SelectionDispatcher dispatcher(d_formatter_p,
                                           d_logStream_p,
                                           bslstl::StringRef(
                                                   selectionInfo.name(),
                                                   selectionInfo.nameLength()),
                                           selectionInfo.formattingMode(),
                                           *d_options_p,
                                           d_isFirstMember);

    int rc =  bdlat_TypeCategoryUtil::accessByCategory(selection, dispatcher);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    d_selectionIsEmpty = dispatcher.selectionIsEmpty();
    return 0;
}

// ACCESSORS
inline
bool Encoder_SelectionVisitor::selectionIsEmpty() const
{
    return d_selectionIsEmpty;
}

                      // ---------------------------------
                      // class Encoder_SelectionDispatcher
                      // ---------------------------------

// CREATORS
inline
Encoder_SelectionDispatcher::Encoder_SelectionDispatcher(
                                      Formatter                *formatter,
                                      bsl::ostream             *logStream,
                                      const bslstl::StringRef&  selectionName,
                                      FormattingMode            formattingMode,
                                      const EncoderOptions&     options,
                                      bool                      isFirstMember)
: d_selectionIsEmpty(false)
, d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_selectionName(selectionName)
, d_formattingMode(formattingMode)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// ACCESSORS
template <class TYPE>
inline
int Encoder_SelectionDispatcher::operator()(const TYPE& selection)
{
    return bdlat_TypeCategoryUtil::accessByCategory(selection, *this);
}

template <class TYPE>
inline
int Encoder_SelectionDispatcher::operator()(
                                  const TYPE&                        selection,
                                  bdlat_TypeCategory::CustomizedType)
{
    return bdlat_TypeCategoryUtil::accessByCategory(
        bdlat_CustomizedTypeFunctions::convertToBaseType(selection), *this);
}

template <class TYPE, class CATEGORY>
inline
int Encoder_SelectionDispatcher::operator()(const TYPE& selection,
                                            CATEGORY    category)
{
    return Encoder_EncodeImplUtil::encodeMember(&d_selectionIsEmpty,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_selectionName,
                                                selection,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isFirstMember,
                                                category);
}

template <class TYPE>
inline
int Encoder_SelectionDispatcher::operator()(const TYPE&, bslmf::Nil)
{
    BSLS_ASSERT_OPT(!"Reachable");
    return -1;
}

// ACCESSORS
inline
bool Encoder_SelectionDispatcher::selectionIsEmpty() const
{
    return d_selectionIsEmpty;
}

                       // ------------------------------
                       // class Encoder_AttributeVisitor
                       // ------------------------------

// CREATORS
inline
Encoder_AttributeVisitor::Encoder_AttributeVisitor(
                                         Formatter             *formatter,
                                         bsl::ostream          *logStream,
                                         const EncoderOptions&  options,
                                         bool                   isFirstMember)
: d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// MANIPULATORS
template <class TYPE, class INFO>
int Encoder_AttributeVisitor::operator()(const TYPE& attribute,
                                         const INFO& attributeInfo)
{
    Encoder_AttributeDispatcher dispatcher(d_formatter_p,
                                           d_logStream_p,
                                           bslstl::StringRef(
                                                   attributeInfo.name(),
                                                   attributeInfo.nameLength()),
                                           attributeInfo.formattingMode(),
                                           *d_options_p,
                                           d_isFirstMember);

    int rc = bdlat_TypeCategoryUtil::accessByCategory(attribute, dispatcher);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    if (!dispatcher.attributeIsEmpty()) {
        d_isFirstMember = false;
    }
    return 0;
}

// ACCESSORS
inline
bool Encoder_AttributeVisitor::attributesAreEmpty() const
{
    return d_isFirstMember;
}

                      // ---------------------------------
                      // class Encoder_AttributeDispatcher
                      // ---------------------------------

// CREATORS
inline
Encoder_AttributeDispatcher::Encoder_AttributeDispatcher(
                                      Formatter                *formatter,
                                      bsl::ostream             *logStream,
                                      const bslstl::StringRef&  attributeName,
                                      FormattingMode            formattingMode,
                                      const EncoderOptions&     options,
                                      bool                      isFirstMember)
: d_attributeIsEmpty(false)
, d_formatter_p(formatter)
, d_logStream_p(logStream)
, d_attributeName(attributeName)
, d_formattingMode(formattingMode)
, d_options_p(&options)
, d_isFirstMember(isFirstMember)
{
}

// MANIPULATORS
template <class TYPE>
inline
int Encoder_AttributeDispatcher::operator()(const TYPE& value)
{
    return bdlat_TypeCategoryUtil::accessByCategory(value, *this);
}

inline
int Encoder_AttributeDispatcher::operator()(
                                           const bsl::vector<char>&  attribute,
                                           bdlat_TypeCategory::Array category)
{
    return Encoder_EncodeImplUtil::encodeMember(&d_attributeIsEmpty,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_attributeName,
                                                attribute,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isFirstMember,
                                                category);
}

template <class TYPE>
inline
int Encoder_AttributeDispatcher::operator()(
                                           const TYPE&               attribute,
                                           bdlat_TypeCategory::Array category)
{
    const bool arrayIsEmpty = (0 == bdlat_ArrayFunctions::size(attribute));

    if (!d_options_p->encodeEmptyArrays() && arrayIsEmpty) {
        d_attributeIsEmpty = true;
        return 0;                                                     // RETURN
    }

    bool memberIsEmpty = false;
    int rc = Encoder_EncodeImplUtil::encodeMember(&memberIsEmpty,
                                                  d_formatter_p,
                                                  d_logStream_p,
                                                  d_attributeName,
                                                  attribute,
                                                  d_formattingMode,
                                                  *d_options_p,
                                                  d_isFirstMember,
                                                  category);
    if (0 != rc) {
        return rc;                                                    // RETURN
    }

    BSLS_ASSERT(false == memberIsEmpty);
        // The member cannot be empty because, if this branch is reached,
        // the array is not empty.  Non-empty arrays always have a non-empty
        // encoding, even if they are untagged attributes.

    d_attributeIsEmpty = false;
    return 0;
}

template <class TYPE>
inline
int Encoder_AttributeDispatcher::operator()(
                                  const TYPE&                        attribute,
                                  bdlat_TypeCategory::CustomizedType)
{
    return bdlat_TypeCategoryUtil::accessByCategory(
        bdlat_CustomizedTypeFunctions::convertToBaseType(attribute), *this);
}

template <class TYPE>
inline
int Encoder_AttributeDispatcher::operator()(
                                   const TYPE&                       attribute,
                                   bdlat_TypeCategory::NullableValue category)
{
    if (bdlat_NullableValueFunctions::isNull(attribute) &&
        !d_options_p->encodeNullElements()) {
        d_attributeIsEmpty = true;
        return 0;                                                     // RETURN
    }

    return Encoder_EncodeImplUtil::encodeMember(&d_attributeIsEmpty,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_attributeName,
                                                attribute,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isFirstMember,
                                                category);
}

template <class TYPE, class CATEGORY>
inline
int Encoder_AttributeDispatcher::operator()(const TYPE& attribute,
                                            CATEGORY    category)
{
    return Encoder_EncodeImplUtil::encodeMember(&d_attributeIsEmpty,
                                                d_formatter_p,
                                                d_logStream_p,
                                                d_attributeName,
                                                attribute,
                                                d_formattingMode,
                                                *d_options_p,
                                                d_isFirstMember,
                                                category);
}

template <class TYPE>
inline
int Encoder_AttributeDispatcher::operator()(const TYPE&, bslmf::Nil)
{
    BSLS_ASSERT_OPT(!"Unreachable");
    return -1;
}

// ACCESSORS
inline
bool Encoder_AttributeDispatcher::attributeIsEmpty() const
{
    return d_attributeIsEmpty;
}

// The 'Encoder_Formatter' 'class' has been replaced by the 'baljsn::Formatter'
// 'class' in the 'baljsn_formatter' component.  Clients should use that
// 'class' instead.  The following 'class' definition is provided for
// backwards-compatibility for users that have written code using this
// component-private 'class'.

                          // =======================
                          // class Encoder_Formatter
                          // =======================

class Encoder_Formatter {
    // This class implements a formatter providing operations for rending JSON
    // text elements to an output stream (supplied at construction) according
    // to a set of formatting options (also supplied at construction).  This is
    // a component-private class and should not be used outside of this
    // component.
    //
    // DEPRECATED: Use 'baljsn::Formatter' instead.

    // DATA
    bsl::ostream& d_outputStream;     // stream for output (held, not owned)
    bool          d_usePrettyStyle;   // encoding style
    int           d_indentLevel;      // initial indent level
    int           d_spacesPerLevel;   // spaces per level
    bool          d_isArrayElement;   // is current element part of an array

  public:
    // CREATORS
    Encoder_Formatter(bsl::ostream& stream, const EncoderOptions& options);
        // Create a 'Encoder_Formatter' object using the specified 'stream' and
        // 'options'.

    //! ~Encoder_Formatter() = default;
        // Destroy this object.

    // MANIPULATORS
    void openObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an object.

    void closeObject();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an object.

    void openArray(bool formatAsEmptyArrayFlag = false);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an array.  Optionally specify
        // 'formatAsEmptyArrayFlag' denoting if the array being opened should
        // be formatted as an empty array.  If 'formatAsEmptyArrayFlag' is not
        // specified then the array being opened is formatted as an array
        // having elements.  Note that the formatting (and as a consequence the
        // 'formatAsEmptyArrayFlag') is relevant only if this formatter encodes
        // in the pretty style and is ignored otherwise.

    void closeArray(bool formatAsEmptyArrayFlag = false);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an array.  Optionally specify
        // 'formatAsEmptyArrayFlag' denoting if the array being closed should
        // be formatted as an empty array.  If 'formatAsEmptyArrayFlag' is not
        // specified then the array being closed is formatted as an array
        // having elements.  Note that the formatting (and as a consequence the
        // 'formatAsEmptyArrayFlag') is relevant only if this formatter encodes
        // in the pretty style and is ignored otherwise.

    void indent();
        // Print onto the stream supplied at construction the sequence of
        // whitespace characters for the proper indentation of an element given
        // the encoding options supplied at construction.

    int openElement(const bsl::string& name);
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of an element having the specified
        // 'name'.  Return 0 on success and a non-zero value otherwise.

    void closeElement();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of an element.

    void openDocument();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the start of the document.

    void closeDocument();
        // Print onto the stream supplied at construction the sequence of
        // characters designating the end of the document.

    void setIsArrayElement(bool isArrayElement);
        // Set the flag denoting if the current element refers to an array
        // element to the specified 'isArrayElement'.

    // ACCESSORS
    bool isArrayElement() const;
        // Return the value of the flag denoting if the current element refers
        // to an array element.
};

                        // -----------------------
                        // class Encoder_Formatter
                        // -----------------------

// MANIPULATORS
inline
void Encoder_Formatter::setIsArrayElement(bool isArrayElement)
{
    d_isArrayElement = isArrayElement;
}

// ACCESSORS
inline
bool Encoder_Formatter::isArrayElement() const
{
    return d_isArrayElement;
}

}  // close package namespace
}  // close enterprise namespace

#endif

// ----------------------------------------------------------------------------
// Copyright 2015 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
